/**
 * Contains {@link AkimovsRouteProvider}
 */
package logic;

import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.LinkedList;
import logic.connectors.Connector;
import logic.pathelements.ActiveElement;
import logic.pathelements.PathElement;
import logic.pathelements.Protocol;

/**
 * Paths searcher
 * @author Alexey Akimov <frinatella@gmail.com>
 */
public class AkimovsRouteProvider extends RouteProvider {
    
    /**
     * Container for path with connector, by which was found this path last element
     */
    private class SearchPath {

        /**
         * Constructor
         * @param inputConnector connector, by which was found this path last element
         * @param path path
         */
        public SearchPath(Connector inputConnector, ArrayList<PathElement> path) {
            this.inputConnector = inputConnector;
            this.path = path;
        }
        
        /**
         * connector, by which was found this path last element
         */
        Connector inputConnector;
        
        /**
         * path
         */
        ArrayList<PathElement> path;
                
    }

    @Override
    public LinkedList<ArrayList<PathElement>> getRoutes(Protocol protocol, Inet4Address srcIp, Inet4Address destIp, int srcPort, int destPort, Network network) throws RouteNotFoundException { 
        LinkedList<ArrayList<PathElement>> findedPaths = new LinkedList<ArrayList<PathElement>>();
        // processed paths (is using as stack)
        LinkedList<SearchPath> paths = new LinkedList<SearchPath>();
        //First Element with srcIp network interface
        PathElement currentElement = network.getPathElementByIp(srcIp);
        ArrayList<PathElement> currentPath = new ArrayList<PathElement>();
        currentPath.add(currentElement);
        //srcIp and destIp both are from one element
        if (((ActiveElement) currentElement).getNetworkInterface(destIp) != null) {
            findedPaths.add(currentPath);
            return findedPaths;
        }
        //adds this path to paths structure with null as connector by which was added current path last element
        paths.add(new SearchPath(null, currentPath));
        Connector inputConnector;
        Connector nextConnector;
        // until all paths were processed
        while(0 < paths.size()) {
            //process first path in processed paths stack
            //connector by which was added last element to procesed path
            inputConnector = paths.get(0).inputConnector;
            //processed path
            currentPath = paths.get(0).path;
            //Process last added path element cdonnectors. 
            //For each of they path will be cloned and added to paths structure, but each with own last element and inputConnector for nex iteration both are connected to processed connector.  
            for(Connector currentConnector : currentPath.get(currentPath.size() - 1).getRouteConnectors(inputConnector, protocol, srcIp, destIp, srcPort, destPort)) {
                //connector connected to currentConnector will be used as currentConnector in next iteration
                nextConnector = currentConnector.connectedTo();
                currentElement = nextConnector.getOwner();
                // Check elements repeats
                if (!currentPath.contains(currentElement)) {
                    //Adds this path to paths structure with nextConnector as connector by which was added current path last element
                    paths.add(new SearchPath(nextConnector, (ArrayList<PathElement>) currentPath.clone()));
                    //Adds to new cloned path new element connected by nextConnector
                    paths.get(paths.size() - 1).path.add(currentElement);
                    if (currentElement  instanceof ActiveElement ) {
                        //If this element contains needed ip (destIp) add this path to findedPaths and remove it from processed paths
                        if (((ActiveElement) currentElement).getNetworkInterface(destIp) != null) {
                            findedPaths.add(paths.get(paths.size() - 1).path);
                            paths.remove(paths.size() - 1);
                        }
                        //If this element contains srcIp throws ip conflict exception
                        if (((ActiveElement) currentElement).getNetworkInterface(srcIp) != null) {
                            throw new RouteNotFoundException("IP addresses conflict");
                        }
                    }
                }
            }
            //remove from stack already processed path
            paths.remove(0);
        }
        return findedPaths;
    }

    @Override
    public LinkedList<ArrayList<PathElement>> getRoutes(Protocol protocol, String srcIp, String destIp, int srcPort, int destPort, Network network) throws RouteNotFoundException, UnknownHostException {
        return getRoutes(protocol, (Inet4Address) Inet4Address.getByName(srcIp), (Inet4Address) Inet4Address.getByName(destIp), srcPort, destPort, network);
    }

    @Override
    public String getDescription() {
        return "This is Akimov's route provider";
    }
    
}
